# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2018.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.


"""
Module for the primary interface to the circuit drawers.

This module contains the end user facing API for drawing quantum circuits.
There are 3 available drawer backends available:

 0. Ascii art
 1. LaTeX
 2. Matplotlib

This provides a single function entrypoint to drawing a circuit object with
any of the backends.
"""

import errno
import logging
import os
import subprocess
import tempfile
from warnings import warn

try:
    from PIL import Image
    HAS_PIL = True
except ImportError:
    HAS_PIL = False

from qiskit import user_config
from qiskit.visualization import exceptions
from qiskit.visualization import latex as _latex
from qiskit.visualization import text as _text
from qiskit.visualization import utils
from qiskit.visualization import matplotlib as _matplotlib

logger = logging.getLogger(__name__)


def circuit_drawer(circuit,
                   scale=None,
                   filename=None,
                   style=None,
                   output=None,
                   interactive=False,
                   plot_barriers=True,
                   reverse_bits=False,
                   justify=None,
                   vertical_compression='medium',
                   idle_wires=True,
                   with_layout=True,
                   fold=None,
                   ax=None,
                   initial_state=False,
                   cregbundle=True):
    """Draw a quantum circuit to different formats (set by output parameter):

    **text**: ASCII art TextDrawing that can be printed in the console.

    **latex**: high-quality images compiled via latex.

    **latex_source**: raw uncompiled latex output.

    **matplotlib**: images with color rendered purely in Python.

    Args:
        circuit (QuantumCircuit): the quantum circuit to draw
        scale (float): scale of image to draw (shrink if < 1). Only used by the ``mpl``,
            ``latex``, and ``latex_source`` outputs.
        filename (str): file path to save image to
        style (dict or str): dictionary of style or file name of style file.
            This option is only used by the ``mpl`` output type. If a str is
            passed in that is the path to a json file which contains that will
            be open, parsed, and then used just as the input dict. See:
            :ref:`Style Dict Doc <style-dict-doc>` for more information on the
            contents.
        output (str): Select the output method to use for drawing the circuit.
            Valid choices are ``text``, ``latex``, ``latex_source``, ``mpl``.
            By default the `'text`' drawer is used unless a user config file
            has an alternative backend set as the default. If the output kwarg
            is set, that backend will always be used over the default in a user
            config file.
        interactive (bool): when set true show the circuit in a new window
            (for `mpl` this depends on the matplotlib backend being used
            supporting this). Note when used with either the `text` or the
            `latex_source` output type this has no effect and will be silently
            ignored.
        reverse_bits (bool): When set to True reverse the bit order inside
            registers for the output visualization.
        plot_barriers (bool): Enable/disable drawing barriers in the output
            circuit. Defaults to True.
        justify (string): Options are ``left``, ``right`` or ``none``, if
            anything else is supplied it defaults to left justified. It refers
            to where gates should be placed in the output circuit if there is
            an option. ``none`` results in each gate being placed in its own
            column.
        vertical_compression (string): ``high``, ``medium`` or ``low``. It
            merges the lines generated by the ``text`` output so the drawing
            will take less vertical room.  Default is ``medium``. Only used by
            the ``text`` output, will be silently ignored otherwise.
        idle_wires (bool): Include idle wires (wires with no circuit elements)
            in output visualization. Default is True.
        with_layout (bool): Include layout information, with labels on the
            physical layout. Default is True.
        fold (int): Sets pagination. It can be disabled using -1.
            In `text`, sets the length of the lines. This useful when the
            drawing does not fit in the console. If None (default), it will try
            to guess the console width using ``shutil.get_terminal_size()``.
            However, if running in jupyter, the default line length is set to
            80 characters. In ``mpl`` it is the number of (visual) layers before
            folding. Default is 25.
        ax (matplotlib.axes.Axes): An optional Axes object to be used for
            the visualization output. If none is specified a new matplotlib
            Figure will be created and used. Additionally, if specified there
            will be no returned Figure since it is redundant. This is only used
            when the ``output`` kwarg is set to use the ``mpl`` backend. It
            will be silently ignored with all other outputs.
        initial_state (bool): Optional. Adds ``|0>`` in the beginning of the wire.
            Default: ``False``.
        cregbundle (bool): Optional. If set True bundle classical registers.
            Default: ``True``.

    Returns:
        :class:`PIL.Image` or :class:`matplotlib.figure` or :class:`str` or
        :class:`TextDrawing`:

        * `PIL.Image` (output='latex')
            an in-memory representation of the image of the circuit diagram.
        * `matplotlib.figure.Figure` (output='mpl')
            a matplotlib figure object for the circuit diagram.
        * `str` (output='latex_source')
            The LaTeX source code for visualizing the circuit diagram.
        * `TextDrawing` (output='text')
            A drawing that can be printed as ascii art

    Raises:
        VisualizationError: when an invalid output method is selected
        ImportError: when the output methods requires non-installed libraries.

    .. _style-dict-doc:

    **Style Dict Details**

    The style dict kwarg contains numerous options that define the style of the
    output circuit visualization. The style dict is only used by the ``mpl``
    output. The options available in the style dict are defined below:

    Args:
        textcolor (str): The color code to use for text. Defaults to
            `'#000000'`
        subtextcolor (str): The color code to use for subtext. Defaults to
            `'#000000'`
        linecolor (str): The color code to use for lines. Defaults to
            `'#000000'`
        creglinecolor (str): The color code to use for classical register
            lines. Defaults to `'#778899'`
        gatetextcolor (str): The color code to use for gate text. Defaults to
            `'#000000'`
        gatefacecolor (str): The color code to use for gates. Defaults to
            `'#ffffff'`
        barrierfacecolor (str): The color code to use for barriers. Defaults to
            `'#bdbdbd'`
        backgroundcolor (str): The color code to use for the background.
            Defaults to `'#ffffff'`
        fontsize (int): The font size to use for text. Defaults to 13
        subfontsize (int): The font size to use for subtext. Defaults to 8
        displaytext (dict): A dictionary of the text to use for each element
            type in the output visualization. The default values are::

                {
                    'id': 'id',
                    'u0': 'U_0',
                    'u1': 'U_1',
                    'u2': 'U_2',
                    'u3': 'U_3',
                    'x': 'X',
                    'y': 'Y',
                    'z': 'Z',
                    'h': 'H',
                    's': 'S',
                    'sdg': 'S^\\dagger',
                    't': 'T',
                    'tdg': 'T^\\dagger',
                    'rx': 'R_x',
                    'ry': 'R_y',
                    'rz': 'R_z',
                    'reset': '\\left|0\\right\\rangle'
                }

            You must specify all the necessary values if using this. There is
            no provision for passing an incomplete dict in.
        displaycolor (dict):
            The color codes to use for each circuit element. The default values are::

                {
                    'id': '#F0E442',
                    'u0': '#E7AB3B',
                    'u1': '#E7AB3B',
                    'u2': '#E7AB3B',
                    'u3': '#E7AB3B',
                    'x': '#58C698',
                    'y': '#58C698',
                    'z': '#58C698',
                    'h': '#70B7EB',
                    's': '#E0722D',
                    'sdg': '#E0722D',
                    't': '#E0722D',
                    'tdg': '#E0722D',
                    'rx': '#ffffff',
                    'ry': '#ffffff',
                    'rz': '#ffffff',
                    'reset': '#D188B4',
                    'target': '#70B7EB',
                    'meas': '#D188B4'
                }

           Also, just like  `displaytext` there is no provision for an
           incomplete dict passed in.

        latexdrawerstyle (bool): When set to True enable latex mode which will
            draw gates like the `latex` output modes.
        usepiformat (bool): When set to True use radians for output
        fold (int): The number of circuit elements to fold the circuit at.
            Defaults to 20
        cregbundle (bool): If set True bundle classical registers
        showindex (bool): If set True draw an index.
        compress (bool): If set True draw a compressed circuit
        figwidth (int): The maximum width (in inches) for the output figure.
        dpi (int): The DPI to use for the output image. Defaults to 150
        margin (list): A list of margin values to adjust spacing around output
            image. Takes a list of 4 ints: [x left, x right, y bottom, y top].
        creglinestyle (str): The style of line to use for classical registers.
            Choices are `'solid'`, `'doublet'`, or any valid matplotlib
            `linestyle` kwarg value. Defaults to `doublet`

    Example:
        .. jupyter-execute::

            from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
            from qiskit.tools.visualization import circuit_drawer
            q = QuantumRegister(1)
            c = ClassicalRegister(1)
            qc = QuantumCircuit(q, c)
            qc.h(q)
            qc.measure(q, c)
            circuit_drawer(qc)
    """
    image = None
    config = user_config.get_config()
    # Get default from config file else use text
    default_output = 'text'
    if config:
        default_output = config.get('circuit_drawer', 'text')
        if default_output == 'auto':
            if _matplotlib.HAS_MATPLOTLIB:
                default_output = 'mpl'
            else:
                default_output = 'text'
    if output is None:
        output = default_output

    if output == 'text':
        return _text_circuit_drawer(circuit, filename=filename,
                                    reverse_bits=reverse_bits,
                                    plot_barriers=plot_barriers,
                                    justify=justify,
                                    vertical_compression=vertical_compression,
                                    idle_wires=idle_wires,
                                    with_layout=with_layout,
                                    fold=fold,
                                    initial_state=initial_state,
                                    cregbundle=cregbundle)
    elif output == 'latex':
        image = _latex_circuit_drawer(circuit, scale=scale,
                                      filename=filename, style=style,
                                      plot_barriers=plot_barriers,
                                      reverse_bits=reverse_bits,
                                      justify=justify,
                                      idle_wires=idle_wires,
                                      with_layout=with_layout,
                                      initial_state=initial_state,
                                      cregbundle=cregbundle)
    elif output == 'latex_source':
        return _generate_latex_source(circuit,
                                      filename=filename, scale=scale,
                                      style=style,
                                      plot_barriers=plot_barriers,
                                      reverse_bits=reverse_bits,
                                      justify=justify,
                                      idle_wires=idle_wires,
                                      with_layout=with_layout,
                                      initial_state=initial_state,
                                      cregbundle=cregbundle)
    elif output == 'mpl':
        image = _matplotlib_circuit_drawer(circuit, scale=scale,
                                           filename=filename, style=style,
                                           plot_barriers=plot_barriers,
                                           reverse_bits=reverse_bits,
                                           justify=justify,
                                           idle_wires=idle_wires,
                                           with_layout=with_layout,
                                           fold=fold,
                                           ax=ax,
                                           initial_state=initial_state,
                                           cregbundle=cregbundle)
    else:
        raise exceptions.VisualizationError(
            'Invalid output type %s selected. The only valid choices '
            'are latex, latex_source, text, and mpl' % output)
    if image and interactive:
        image.show()
    return image


# -----------------------------------------------------------------------------
# Plot style sheet option
# -----------------------------------------------------------------------------
def qx_color_scheme():
    """Return default style for matplotlib_circuit_drawer (IBM QX style)."""
    warn('The qx_color_scheme function is deprecated as of 0.11, and '
         'will be removed no earlier than 3 months after that release '
         'date.', DeprecationWarning, stacklevel=2)
    return {
        "comment": "Style file for matplotlib_circuit_drawer (IBM QX Composer style)",
        "textcolor": "#000000",
        "gatetextcolor": "#000000",
        "subtextcolor": "#000000",
        "linecolor": "#000000",
        "creglinecolor": "#b9b9b9",
        "gatefacecolor": "#ffffff",
        "barrierfacecolor": "#bdbdbd",
        "backgroundcolor": "#ffffff",
        "fold": 20,
        "fontsize": 13,
        "subfontsize": 8,
        "figwidth": -1,
        "dpi": 150,
        "displaytext": {
            "id": "id",
            "u0": "U_0",
            "u1": "U_1",
            "u2": "U_2",
            "u3": "U_3",
            "x": "X",
            "y": "Y",
            "z": "Z",
            "h": "H",
            "s": "S",
            "sdg": "S^\\dagger",
            "t": "T",
            "tdg": "T^\\dagger",
            "rx": "R_x",
            "ry": "R_y",
            "rz": "R_z",
            "reset": "\\left|0\\right\\rangle"
        },
        "displaycolor": {
            "id": "#ffca64",
            "u0": "#f69458",
            "u1": "#f69458",
            "u2": "#f69458",
            "u3": "#f69458",
            "x": "#a6ce38",
            "y": "#a6ce38",
            "z": "#a6ce38",
            "h": "#00bff2",
            "s": "#00bff2",
            "sdg": "#00bff2",
            "t": "#ff6666",
            "tdg": "#ff6666",
            "rx": "#ffca64",
            "ry": "#ffca64",
            "rz": "#ffca64",
            "reset": "#d7ddda",
            "target": "#00bff2",
            "meas": "#f070aa"
        },
        "latexdrawerstyle": True,
        "usepiformat": False,
        "cregbundle": False,
        "showindex": False,
        "compress": True,
        "margin": [2.0, 0.0, 0.0, 0.3],
        "creglinestyle": "solid",
        "reversebits": False
    }


# -----------------------------------------------------------------------------
# _text_circuit_drawer
# -----------------------------------------------------------------------------


def _text_circuit_drawer(circuit, filename=None, reverse_bits=False,
                         plot_barriers=True, justify=None, vertical_compression='high',
                         idle_wires=True, with_layout=True, fold=None, initial_state=True,
                         cregbundle=False):
    """Draws a circuit using ascii art.

    Args:
        circuit (QuantumCircuit): Input circuit
        filename (str): optional filename to write the result
        reverse_bits (bool): Rearrange the bits in reverse order.
        plot_barriers (bool): Draws the barriers when they are there.
        justify (str) : `left`, `right` or `none`. Defaults to `left`. Says how
                        the circuit should be justified.
        vertical_compression (string): `high`, `medium`, or `low`. It merges the
            lines so the drawing will take less vertical room. Default is `high`.
        idle_wires (bool): Include idle wires. Default is True.
        with_layout (bool): Include layout information, with labels on the physical
            layout. Default: True
        fold (int): Optional. Breaks the circuit drawing to this length. This
                    useful when the drawing does not fit in the console. If
                    None (default), it will try to guess the console width using
                    `shutil.get_terminal_size()`. If you don't want pagination
                   at all, set `fold=-1`.
        initial_state (bool): Optional. Adds |0> in the beginning of the line. Default: `True`.
        cregbundle (bool): Optional. If set True bundle classical registers. Only used by
            the ``text`` output. Default: ``False``.
    Returns:
        TextDrawing: An instances that, when printed, draws the circuit in ascii art.
    """
    qregs, cregs, ops = utils._get_layered_instructions(circuit,
                                                        reverse_bits=reverse_bits,
                                                        justify=justify,
                                                        idle_wires=idle_wires)
    if with_layout:
        layout = circuit._layout
    else:
        layout = None
    global_phase = circuit.global_phase if hasattr(circuit, 'global_phase') else None
    text_drawing = _text.TextDrawing(qregs, cregs, ops, layout=layout, initial_state=initial_state,
                                     cregbundle=cregbundle, global_phase=global_phase)
    text_drawing.plotbarriers = plot_barriers
    text_drawing.line_length = fold
    text_drawing.vertical_compression = vertical_compression

    if filename:
        text_drawing.dump(filename)
    return text_drawing


# -----------------------------------------------------------------------------
# latex_circuit_drawer
# -----------------------------------------------------------------------------


def _latex_circuit_drawer(circuit,
                          scale=0.7,
                          filename=None,
                          style=None,
                          plot_barriers=True,
                          reverse_bits=False,
                          justify=None,
                          idle_wires=True,
                          with_layout=True,
                          initial_state=False,
                          cregbundle=False):
    """Draw a quantum circuit based on latex (Qcircuit package)

    Requires version >=2.6.0 of the qcircuit LaTeX package.

    Args:
        circuit (QuantumCircuit): a quantum circuit
        scale (float): scaling factor
        filename (str): file path to save image to
        style (dict or str): dictionary of style or file name of style file
        reverse_bits (bool): When set to True reverse the bit order inside
            registers for the output visualization.
        plot_barriers (bool): Enable/disable drawing barriers in the output
            circuit. Defaults to True.
        justify (str) : `left`, `right` or `none`. Defaults to `left`. Says how
                        the circuit should be justified.
        idle_wires (bool): Include idle wires. Default is True.
        with_layout (bool): Include layout information, with labels on the physical
            layout. Default: True
        initial_state (bool): Optional. Adds |0> in the beginning of the line. Default: `False`.
        cregbundle (bool): Optional. If set True bundle classical registers.
            Default: ``False``.

    Returns:
        PIL.Image: an in-memory representation of the circuit diagram

    Raises:
        OSError: usually indicates that ```pdflatex``` or ```pdftocairo``` is
                 missing.
        CalledProcessError: usually points errors during diagram creation.
        ImportError: if pillow is not installed
    """
    tmpfilename = 'circuit'
    with tempfile.TemporaryDirectory() as tmpdirname:
        tmppath = os.path.join(tmpdirname, tmpfilename + '.tex')
        _generate_latex_source(circuit, filename=tmppath,
                               scale=scale, style=style,
                               plot_barriers=plot_barriers,
                               reverse_bits=reverse_bits, justify=justify,
                               idle_wires=idle_wires, with_layout=with_layout,
                               initial_state=initial_state,
                               cregbundle=cregbundle)
        try:

            subprocess.run(["pdflatex", "-halt-on-error",
                            "-output-directory={}".format(tmpdirname),
                            "{}".format(tmpfilename + '.tex')],
                           stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
                           check=True)
        except OSError as ex:
            if ex.errno == errno.ENOENT:
                logger.warning('WARNING: Unable to compile latex. '
                               'Is `pdflatex` installed? '
                               'Skipping latex circuit drawing...')
            raise
        except subprocess.CalledProcessError as ex:
            with open('latex_error.log', 'wb') as error_file:
                error_file.write(ex.stdout)
            logger.warning('WARNING Unable to compile latex. '
                           'The output from the pdflatex command can '
                           'be found in latex_error.log')
            raise
        else:
            if not HAS_PIL:
                raise ImportError('The latex drawer needs pillow installed. '
                                  'Run "pip install pillow" before using the '
                                  'latex drawer.')
            try:
                base = os.path.join(tmpdirname, tmpfilename)
                subprocess.run(["pdftocairo", "-singlefile", "-png", "-q",
                                base + '.pdf', base], check=True)
                image = Image.open(base + '.png')
                image = utils._trim(image)
                os.remove(base + '.png')
                if filename:
                    image.save(filename, 'PNG')
            except (OSError, subprocess.CalledProcessError) as ex:
                logger.warning('WARNING: Unable to convert pdf to image. '
                               'Is `poppler` installed? '
                               'Skipping circuit drawing...')
                raise
        return image


def _generate_latex_source(circuit, filename=None,
                           scale=0.7, style=None, reverse_bits=False,
                           plot_barriers=True, justify=None, idle_wires=True,
                           with_layout=True, initial_state=False, cregbundle=False):
    """Convert QuantumCircuit to LaTeX string.

    Args:
        circuit (QuantumCircuit): input circuit
        scale (float): image scaling
        filename (str): optional filename to write latex
        style (dict or str): dictionary of style or file name of style file
        reverse_bits (bool): When set to True reverse the bit order inside
            registers for the output visualization.
        plot_barriers (bool): Enable/disable drawing barriers in the output
            circuit. Defaults to True.
        justify (str) : `left`, `right` or `none`. Defaults to `left`. Says how
                        the circuit should be justified.
        idle_wires (bool): Include idle wires. Default is True.
        with_layout (bool): Include layout information, with labels on the physical
            layout. Default: True
        initial_state (bool): Optional. Adds |0> in the beginning of the line. Default: `False`.
        cregbundle (bool): Optional. If set True bundle classical registers.
            Default: ``False``.

    Returns:
        str: Latex string appropriate for writing to file.
    """
    qregs, cregs, ops = utils._get_layered_instructions(circuit,
                                                        reverse_bits=reverse_bits,
                                                        justify=justify, idle_wires=idle_wires)
    if with_layout:
        layout = circuit._layout
    else:
        layout = None

    global_phase = circuit.global_phase if hasattr(circuit, 'global_phase') else None
    qcimg = _latex.QCircuitImage(qregs, cregs, ops, scale, style=style,
                                 plot_barriers=plot_barriers,
                                 reverse_bits=reverse_bits, layout=layout,
                                 initial_state=initial_state,
                                 cregbundle=cregbundle,
                                 global_phase=global_phase)
    latex = qcimg.latex()
    if filename:
        with open(filename, 'w') as latex_file:
            latex_file.write(latex)

    return latex


# -----------------------------------------------------------------------------
# matplotlib_circuit_drawer
# -----------------------------------------------------------------------------


def _matplotlib_circuit_drawer(circuit,
                               scale=None,
                               filename=None,
                               style=None,
                               plot_barriers=True,
                               reverse_bits=False,
                               justify=None,
                               idle_wires=True,
                               with_layout=True,
                               fold=None,
                               ax=None,
                               initial_state=False,
                               cregbundle=True):

    """Draw a quantum circuit based on matplotlib.
    If `%matplotlib inline` is invoked in a Jupyter notebook, it visualizes a circuit inline.
    We recommend `%config InlineBackend.figure_format = 'svg'` for the inline visualization.

    Args:
        circuit (QuantumCircuit): a quantum circuit
        scale (float): scaling factor
        filename (str): file path to save image to
        style (dict or str): dictionary of style or file name of style file
        reverse_bits (bool): When set to True reverse the bit order inside
            registers for the output visualization.
        plot_barriers (bool): Enable/disable drawing barriers in the output
            circuit. Defaults to True.
        justify (str): `left`, `right` or `none`. Defaults to `left`. Says how
            the circuit should be justified.
        idle_wires (bool): Include idle wires. Default is True.
        with_layout (bool): Include layout information, with labels on the physical
            layout. Default: True.
        fold (int): amount ops allowed before folding. Default is 25.
        ax (matplotlib.axes.Axes): An optional Axes object to be used for
            the visualization output. If none is specified a new matplotlib
            Figure will be created and used. Additionally, if specified there
            will be no returned Figure since it is redundant.
        initial_state (bool): Optional. Adds |0> in the beginning of the line.
            Default: `False`.
        cregbundle (bool): Optional. If set True bundle classical registers.
            Default: ``True``.

    Returns:
        matplotlib.figure: a matplotlib figure object for the circuit diagram
            if the ``ax`` kwarg is not set.
    """

    qregs, cregs, ops = utils._get_layered_instructions(circuit,
                                                        reverse_bits=reverse_bits,
                                                        justify=justify,
                                                        idle_wires=idle_wires)
    if with_layout:
        layout = circuit._layout
    else:
        layout = None

    if fold is None:
        fold = 25

    global_phase = circuit.global_phase if hasattr(circuit, 'global_phase') else None
    qcd = _matplotlib.MatplotlibDrawer(qregs, cregs, ops, scale=scale, style=style,
                                       plot_barriers=plot_barriers,
                                       reverse_bits=reverse_bits, layout=layout,
                                       fold=fold, ax=ax, initial_state=initial_state,
                                       cregbundle=cregbundle, global_phase=global_phase)
    return qcd.draw(filename)
